package darknet.application.chat.impl.net;

import darknet.application.chat.impl.net.message.SessionExitMessage;
import darknet.application.chat.impl.net.message.BroadcastExitChannelMessage;
import darknet.application.chat.impl.net.message.BroadcastChannelMessage;
import darknet.application.chat.impl.net.message.ListChannelMessage;
import darknet.application.chat.impl.net.message.SessionStartMessage;
import darknet.application.chat.impl.net.message.PrivateMessage;
import darknet.application.chat.impl.net.message.ExitChannelMessage;
import darknet.application.chat.impl.net.message.ChannelMessage;
import darknet.application.chat.impl.net.message.JoinChannelMessage;
import darknet.application.chat.impl.net.message.BroadcastJoinChannelMessage;
import darknet.application.chat.impl.net.message.CloseChannelMessage;
import darknet.application.chat.impl.net.response.JoinChannelResponse;
import darknet.application.chat.impl.net.response.ListChannelResponse;
import darknet.application.chat.spec.Channel;
import darknet.application.chat.spec.ChatApplication;
import darknet.application.chat.spec.MessageObserver;

import darknet.core.spec.net.Message;
import darknet.core.spec.net.Node;
import darknet.core.spec.net.ProtocolHandler;
import darknet.core.spec.net.StreamableMessage;

import java.io.ObjectOutputStream;
import java.io.IOException;
import java.util.Set;

/**
 * Implementation of protocol handler for Chat system.
 * 
 * @author Yoyo
 */
public class ChatProtocolHandler implements ProtocolHandler {

    // TODO Add handler for broadcast exit and broadcast channel message.

    /** Chat protocol */
    private ChatProtocol protocol = ChatProtocol.getCurrent();

    /** Chat application */
    private ChatApplication application = null;

    /** Message observer */
    private MessageObserver observer = null;

    /**
     * Initialise a new chat protocol handler.
     * 
     * @param application The chat application.
     */
    public ChatProtocolHandler(
            ChatApplication application, MessageObserver observer) {

        if (application == null) {
            throw new IllegalArgumentException("application is null");
        }
        this.application = application;

        if (observer == null) {
            throw new IllegalArgumentException("observer is invalid (null)");
        }
        this.observer = observer;
    }

    @Override
    public boolean canHandle(Message toHand) {

        if (toHand == null) {
            throw new IllegalArgumentException("toHand is null");
        }

        return protocol.equals(toHand.getHeader().getProtocol());
    }

    @Override
    public void handle(StreamableMessage toHand) throws IOException {

        if (toHand == null) {
            throw new IllegalArgumentException("toHand is null");
        }

        handleMessage(toHand);
    }

    private void handleMessage(StreamableMessage toHand) throws IOException {

        if (toHand.getWrapped() instanceof PrivateMessage) {

            handlePrivateMessage(toHand);

        } else if (toHand.getWrapped() instanceof ChannelMessage) {

            handleChannelMessage(toHand);

        } else if (toHand.getWrapped() instanceof BroadcastChannelMessage) {

            handleBroadcastMessage(toHand);

        } else if (toHand.getWrapped() instanceof ExitChannelMessage) {

            handleExitChannelMessage(toHand);

        } else if (toHand.getWrapped() instanceof ListChannelMessage) {

            handleListChannelMessage(toHand);

        } else if (toHand.getWrapped() instanceof SessionStartMessage) {

            handleSessionStartMessage(toHand);

        } else if (toHand.getWrapped() instanceof SessionExitMessage) {

            handleSessionExitMessage(toHand);

        } else if (toHand.getWrapped() instanceof CloseChannelMessage) {

            handleCloseChannelMessage(toHand);

        } else if (toHand.getWrapped() instanceof JoinChannelMessage) {

            handleJoinChannelMessage(toHand);

        } else if (toHand.getWrapped() instanceof BroadcastJoinChannelMessage) {

            handleBroadcastJoinChannelMessage(toHand);

        } else if (toHand.getWrapped() instanceof BroadcastExitChannelMessage) {

            handleBroadcastExitChannelMessage(toHand);
        }
    }

    private void handleSessionStartMessage(StreamableMessage toHand) 
            throws IOException {
        
        toHand.close();
        SessionStartMessage message = (SessionStartMessage) toHand.getWrapped();
        observer.newSessionStartMessage(message);
    }

    private void handlePrivateMessage(StreamableMessage toHand)
            throws IOException {
        
        toHand.close();
        PrivateMessage message = (PrivateMessage) toHand.getWrapped();
        observer.newPrivateMessage(message);
    }

    private void handleSessionExitMessage(StreamableMessage toHand) 
            throws IOException {

        toHand.close();
        SessionExitMessage message = (SessionExitMessage) toHand.getWrapped();
        observer.newSessionExitMessage(message);
    }

    private void handleListChannelMessage(StreamableMessage toHand)
            throws IOException {

        // send the list of hosted channel, and close the connection.
        ObjectOutputStream out = new ObjectOutputStream(toHand.getOutput());
        Set<Channel> channels = application.getHostedChannels();
        out.writeObject(new ListChannelResponse(channels));
        out.flush();
        toHand.close();
    }

    private void handleJoinChannelMessage(StreamableMessage toHand) 
            throws IOException {

        JoinChannelMessage message = (JoinChannelMessage) toHand.getWrapped();

        ObjectOutputStream out = new ObjectOutputStream(toHand.getOutput());
        Set<Node> nodes = application.getChannel(
                message.getChannelName()).getNodes();
        out.writeObject(new JoinChannelResponse(nodes));
        out.flush();
        toHand.close();
        
        observer.newJoinChannelMessage(message);
    }

    private void handleBroadcastJoinChannelMessage(StreamableMessage toHand)
            throws IOException {

        toHand.close();
        BroadcastJoinChannelMessage message =
                (BroadcastJoinChannelMessage) toHand.getWrapped();
        observer.newBroadcastJoinChannelMessage(message);
    }

    private void handleChannelMessage(StreamableMessage toHand)
            throws IOException {

        toHand.close();
        ChannelMessage message = (ChannelMessage) toHand.getWrapped();
        observer.newChannelMessage(message);

    }

    private void handleBroadcastMessage(StreamableMessage toHand)
            throws IOException {

        toHand.close();
        BroadcastChannelMessage message =
                (BroadcastChannelMessage) toHand.getWrapped();
        observer.newBroadcastChannelMessage(message);
    }

    
    private void handleExitChannelMessage(StreamableMessage toHand) 
            throws IOException {

        toHand.close();
        ExitChannelMessage message = (ExitChannelMessage) toHand.getWrapped();
        observer.newExitChannelMessage(message);
    }

    private void handleBroadcastExitChannelMessage(StreamableMessage toHand)
            throws IOException {

        toHand.close();
        BroadcastExitChannelMessage message =
                (BroadcastExitChannelMessage) toHand.getWrapped();
        observer.newBroadcastExitChannelMessage(message);
    }
    

    private void handleCloseChannelMessage(StreamableMessage toHand) 
            throws IOException {
        
        toHand.close();
        CloseChannelMessage message =
                (CloseChannelMessage) toHand.getWrapped();
        observer.newCloseChannelMessage(message);
    }
}
